using System;
using System.IO;

namespace SpoonCore
{
	public class SpoonIdentifier : IComparable<SpoonIdentifier>
	{
		private const int Size = 16;
		private static readonly Random _rand = new Random();

		private readonly byte[] _data = new byte[Size];

		public SpoonIdentifier()
		{
			_rand.NextBytes(_data);
		}

		public SpoonIdentifier(BinaryReader br)
		{
			_data = br.ReadBytes(_data.Length);
		}

		private SpoonIdentifier(byte[] data)
		{
			_data = data;
		}

		public byte ValueAt(int index)
		{
			int pos = (int)Math.Floor((double)index / 2);
			int mask = 0xF;
			int shift = 0;
			if (index % 2 == 0) shift = 4;
			return (byte)((_data[pos] & (mask << shift)) >> shift);
		}

		public int Count
		{
			get { return _data.Length * 2; }
		}

		public static SpoonIdentifier Empty
		{
			get { return new SpoonIdentifier(new byte[Size]); }
		}

		public override int GetHashCode()
		{
			return _data.GetHashCode();
		}

		public override bool Equals(object obj)
		{
			if (obj is SpoonIdentifier)
			{
				return ((SpoonIdentifier)obj) == this;
			}
			else return base.Equals(obj);
		}

		public override string ToString()
		{
			string tmp = "";
			foreach (byte i in _data)
			{
				tmp += i.ToString("X2");
			}
			return tmp;
		}

		public byte LevelTo(SpoonIdentifier id)
		{
			byte res;

			for (res = 0; res < Count; res++)
			{
				if (id.ValueAt(res) != ValueAt(res)) break;
			}

			return res;
		}

		public int CompareTo(SpoonIdentifier other)
		{
			for (int x = 0; x < _data.Length; x++)
			{
				if (_data[x] < other._data[x]) return -1;
				if (_data[x] > other._data[x]) return 1;
			}
			return 0;
		}

		public static bool operator ==(SpoonIdentifier o1, SpoonIdentifier o2)
		{
			for (int x = 0; x < o1._data.Length; x++)
			{
				if (o1._data[x] != o2._data[x]) return false;
			}
			return true;
		}

		public static bool operator !=(SpoonIdentifier o1, SpoonIdentifier o2)
		{
			return !(o1 == o2);
		}

		public static bool operator <(SpoonIdentifier o1, SpoonIdentifier o2)
		{
			for (int x = 0; x < o1._data.Length; x++)
			{
				if (o1._data[x] < o2._data[x]) return true;
			}
			return false;
		}

		public static bool operator >(SpoonIdentifier o1, SpoonIdentifier o2)
		{
			return !(o1 < o2);
		}

		public static SpoonIdentifier operator -(SpoonIdentifier o1, SpoonIdentifier o2)
		{
			byte[] data = new byte[o1._data.Length];

			if (o1 < o2)
			{
				SpoonIdentifier tmp = o2;
				o2 = o1;
				o1 = tmp;
			}

			for (int x = data.Length - 1; x >= 0; x--)
			{
				int uebertrag = (o2._data[x] - o1._data[x] < 0) ? 1 : 0;
				data[x] = (byte)(o2._data[x] - o1._data[x] + uebertrag);
			}

			return new SpoonIdentifier(data);
		}

		public byte[] GetBytes()
		{
			return _data;
		}

		public SpoonIdentifier NearestDown(SpoonIdentifier id1, SpoonIdentifier id2)
		{
			return Nearest(id1, id2, false);
		}

		public SpoonIdentifier NearestUp(SpoonIdentifier id1, SpoonIdentifier id2)
		{
			return Nearest(id1, id2, true);
		}

		private SpoonIdentifier Nearest(SpoonIdentifier id1, SpoonIdentifier id2, bool up)
		{
			if (id1 == id2) return id1;
			int zero = 0;

			for (int x = 0; x < _data.Length; x++)
			{
				if (id1._data[x] != id2._data[x])
				{
					int div1, div2;
					if (up)
					{
						div1 = id1._data[x] - _data[x];
						div2 = id2._data[x] - _data[x];
					}
					else
					{
						div1 = _data[x] - id1._data[x];
						div2 = _data[x] - id2._data[x];
					}

					if (zero == 1)
					{
						if (div1 == 0) continue;
						if (div1 > 0) return id1;
						else return id2;
					}
					if (zero == 2)
					{
						if (div2 == 0) continue;
						if (div2 > 0) return id2;
						else return id1;
					}

					if ((div1 > 0 && div2 > 0) || (div1 < 0 && div2 < 0))
					{
						if (div1 < div2) return id1;
						else return id2;
					}
					else if (div1 < 0 && div2 > 0) return id2;
					else if (div1 > 0 && div2 < 0) return id1;
					else
					{
						if (div1 == 0) zero = 1;
						else zero = 2;
					}
				}
			}

			return null;
		}
	}
}